﻿namespace WWDemon.Core.MessageHub
{
    using System;
    using System.Diagnostics;
    using System.Threading.Tasks;
    using WWDemon.Log;
    using WWDemon.Model;

    /// <summary>
    /// An implementation of the <c>Event Aggregator</c> pattern.
    /// </summary>
    public sealed class MessagePub : IMessageHub
    {
        private readonly Subscriptions _subscriptions;
        private Action<Type, object> _globalHandler;
        private Action<Guid, Exception> _globalErrorHandler;

        /// <summary>
        /// Creates an instance of the <see cref="MessageHub"/>.
        /// </summary>
        public MessagePub() => _subscriptions = new Subscriptions();

        /// <summary>
        /// Registers a callback which is invoked on every message published by the <see cref="MessageHub"/>.
        /// <remarks>Invoking this method with a new <paramref name="onMessage"/>overwrites the previous one.</remarks>
        /// </summary>
        /// <param name="onMessage">
        /// The callback to invoke on every message
        /// <remarks>The callback receives the type of the message and the message as arguments</remarks>
        /// </param>
        public void RegisterGlobalHandler(Action<Type, object> onMessage)
        {
            EnsureNotNull(onMessage);
            _globalHandler = onMessage;
        }

        /// <summary>
        /// Invoked if an error occurs when publishing a message to a subscriber.
        /// <remarks>Invoking this method with a new <paramref name="onError"/>overwrites the previous one.</remarks>
        /// </summary>
        public void RegisterGlobalErrorHandler(Action<Guid, Exception> onError)
        {
            EnsureNotNull(onError);
            _globalErrorHandler = onError;
        }

        /// <summary>
        /// Publishes the <paramref name="message"/> on the <see cref="MessageHub"/>.
        /// </summary>
        /// <param name="message">The message to published</param>
        public void Publish<T>(CommondKey k,T message)
        {
            var localSubscriptions = _subscriptions.GetTheLatestSubscriptions();

            var msgType = typeof(T);


            _globalHandler?.Invoke(msgType, message);

            // ReSharper disable once ForCanBeConvertedToForeach | Performance Critical
            for (var idx = 0; idx < localSubscriptions.Count; idx++)
            {
                var subscription = localSubscriptions[idx];
                int EnumK = (int)(object)k;

                if (subscription.EnumK != EnumK) { continue; }
                try
                {
                    subscription.Handle(message);
                }
                catch (Exception e)
                {
                    LogHelper.Log(LogDefine.LogError, e, $"订阅执行异常:{EnumK}");
                    _globalErrorHandler?.Invoke(subscription.Token, e);
                }
            }
        }

        /// <summary>
        /// Publishes the <paramref name="message"/> on the <see cref="MessageHub"/>.
        /// </summary>
        /// <param name="message">The message to published</param>
        public void PublishAsync<T>(CommondKey k, T message)
        {
            var localSubscriptions = _subscriptions.GetTheLatestSubscriptions();

            var msgType = typeof(T);


            _globalHandler?.Invoke(msgType, message);

            // ReSharper disable once ForCanBeConvertedToForeach | Performance Critical
            for (var idx = 0; idx < localSubscriptions.Count; idx++)
            {
                var subscription = localSubscriptions[idx];
                int EnumK = (int)(object)k;

                if (subscription.EnumK != EnumK) { continue; }
                try
                {
                    Task.Factory.StartNew(s => {
                        Subscription obj = (Subscription)s;
                        obj.Handle(message);
                    }, subscription);
                }
                catch (Exception e)
                {
                    LogHelper.Log(LogDefine.LogError, e, $"异步订阅执行异常:{EnumK}");
                    _globalErrorHandler?.Invoke(subscription.Token, e);
                }
            }
        }


        /// <summary>
        /// Subscribes a callback against the <see cref="MessageHub"/> for a specific type of message.
        /// </summary>
        /// <typeparam name="T">The type of message to subscribe to</typeparam>
        /// <param name="action">The callback to be invoked once the message is published on the <see cref="MessageHub"/></param>
        /// <returns>The token representing the subscription</returns>
        public Guid Subscribe<T>(CommondKey k, Action<T> action) 
        {
            return Subscribe(k, action, TimeSpan.Zero);
        }

        /// <summary>
        /// Subscribes a callback against the <see cref="MessageHub"/> for a specific type of message.
        /// </summary>
        /// <typeparam name="T">The type of message to subscribe to</typeparam>
        /// <param name="action">The callback to be invoked once the message is published on the <see cref="MessageHub"/></param>
        /// <param name="throttleBy">The <see cref="TimeSpan"/> specifying the rate at which subscription is throttled</param>
        /// <returns>The token representing the subscription</returns>
        public Guid Subscribe<T>(CommondKey k, Action<T> action, TimeSpan throttleBy)
        {
            EnsureNotNull(action);
            return _subscriptions.Register(k,throttleBy, action);
        }

        /// <summary>
        /// Unsubscribes a subscription from the <see cref="MessageHub"/>.
        /// </summary>
        /// <param name="token">The token representing the subscription</param>
        public void Unsubscribe(Guid token) => _subscriptions.UnRegister(token);

        /// <summary>
        /// Checks if a specific subscription is active on the <see cref="MessageHub"/>.
        /// </summary>
        /// <param name="token">The token representing the subscription</param>
        /// <returns><c>True</c> if the subscription is active otherwise <c>False</c></returns>
        public bool IsSubscribed(Guid token) => _subscriptions.IsRegistered(token);

        /// <summary>
        /// Clears all the subscriptions from the <see cref="MessageHub"/>.
        /// <remarks>The global handler and the global error handler are not affected</remarks>
        /// </summary>
        public void ClearSubscriptions() => _subscriptions.Clear(false);

        /// <summary>
        /// Disposes the <see cref="MessageHub"/>.
        /// </summary>
        public void Dispose()
        {
            _globalHandler = null;
            _globalErrorHandler = null;
            _subscriptions.Clear(true);
        }

        [DebuggerStepThrough]
        private void EnsureNotNull(object obj)
        {
            if (obj is null) { throw new NullReferenceException(nameof(obj)); }
        }
    }
}
